xref: /haiku/src/kits/tracker/TextWidget.cpp (revision 93aeb8c3bc3f13cb1f282e3e749258a23790d947)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 #include <string.h>
36 #include <stdlib.h>
37 
38 #include <Alert.h>
39 #include <Debug.h>
40 #include <Directory.h>
41 #include <MessageFilter.h>
42 #include <ScrollView.h>
43 #include <TextView.h>
44 #include <Volume.h>
45 #include <Window.h>
46 
47 #include "Attributes.h"
48 #include "ContainerWindow.h"
49 #include "Commands.h"
50 #include "FSUtils.h"
51 #include "PoseView.h"
52 #include "TextWidget.h"
53 #include "Utilities.h"
54 #include "WidgetAttributeText.h"
55 
56 
57 const float kWidthMargin = 20;
58 
59 
60 BTextWidget::BTextWidget(Model *model, BColumn *column, BPoseView *view)
61 	:
62 	fText(WidgetAttributeText::NewWidgetText(model, column, view)),
63 	fAttrHash(column->AttrHash()),
64 	fAlignment(column->Alignment()),
65 	fEditable(column->Editable()),
66 	fVisible(true),
67 	fActive(false),
68 	fSymLink(model->IsSymLink())
69 {
70 }
71 
72 
73 BTextWidget::~BTextWidget()
74 {
75 	delete fText;
76 }
77 
78 
79 int
80 BTextWidget::Compare(const BTextWidget &with, BPoseView *view) const
81 {
82 	return fText->Compare(*with.fText, view);
83 }
84 
85 
86 void
87 BTextWidget::RecalculateText(const BPoseView *view)
88 {
89 	fText->SetDirty(true);
90 	fText->CheckViewChanged(view);
91 }
92 
93 
94 const char *
95 BTextWidget::Text() const
96 {
97 	StringAttributeText *textAttribute = dynamic_cast<StringAttributeText *>(fText);
98 
99 	ASSERT(textAttribute);
100 	if (!textAttribute)
101 		return "";
102 
103 	return textAttribute->Value();
104 }
105 
106 
107 float
108 BTextWidget::TextWidth(const BPoseView *pose) const
109 {
110 	return fText->Width(pose);
111 }
112 
113 
114 float
115 BTextWidget::PreferredWidth(const BPoseView *pose) const
116 {
117 	return fText->PreferredWidth(pose) + 1;
118 }
119 
120 
121 BRect
122 BTextWidget::ColumnRect(BPoint poseLoc, const BColumn *column,
123 	const BPoseView *view)
124 {
125 	if (view->ViewMode() != kListMode) {
126 		// ColumnRect only makes sense in list view, return
127 		// CalcRect otherwise
128 		return CalcRect(poseLoc, column, view);
129 	}
130 	BRect result;
131 	result.left = column->Offset() + poseLoc.x;
132 	result.right = result.left + column->Width();
133 	result.bottom = poseLoc.y + view->ListElemHeight() - 1;
134 	result.top = result.bottom - view->FontHeight();
135 	return result;
136 }
137 
138 
139 BRect
140 BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn *column,
141 	const BPoseView *view, float textWidth)
142 {
143 	BRect result;
144 	if (view->ViewMode() == kListMode) {
145 		poseLoc.x += column->Offset();
146 
147 		switch (fAlignment) {
148 			case B_ALIGN_LEFT:
149 				result.left = poseLoc.x;
150 				result.right = result.left + textWidth + 1;
151 				break;
152 
153 			case B_ALIGN_CENTER:
154 				result.left = poseLoc.x + (column->Width() / 2) - (textWidth / 2);
155 				if (result.left < 0)
156 					result.left = 0;
157 				result.right = result.left + textWidth + 1;
158 				break;
159 
160 			case B_ALIGN_RIGHT:
161 				result.right = poseLoc.x + column->Width();
162 				result.left = result.right - textWidth - 1;
163 				if (result.left < 0)
164 					result.left = 0;
165 				break;
166 			default:
167 				TRESPASS();
168 		}
169 
170 		result.bottom = poseLoc.y + (view->ListElemHeight() - 1);
171 	} else {
172 		if (view->ViewMode() == kIconMode)
173 			result.left = poseLoc.x + (B_LARGE_ICON - textWidth) / 2;
174 		else
175 			// MINI_ICON_MODE rect calc
176 			result.left = poseLoc.x + B_MINI_ICON + kMiniIconSeparator;
177 
178 		result.right = result.left + textWidth;
179 		result.bottom = poseLoc.y + view->IconPoseHeight();
180 
181 	}
182 	result.top = result.bottom - view->FontHeight();
183 
184 	return result;
185 }
186 
187 
188 BRect
189 BTextWidget::CalcRect(BPoint poseLoc, const BColumn *column,
190 	const BPoseView *view)
191 {
192 	return CalcRectCommon(poseLoc, column, view, fText->Width(view));
193 }
194 
195 
196 BRect
197 BTextWidget::CalcOldRect(BPoint poseLoc, const BColumn *column,
198 	const BPoseView *view)
199 {
200 	return CalcRectCommon(poseLoc, column, view, fText->CurrentWidth());
201 }
202 
203 
204 BRect
205 BTextWidget::CalcClickRect(BPoint poseLoc, const BColumn *column,
206 	const BPoseView* view)
207 {
208 	BRect result = CalcRect(poseLoc, column, view);
209 	if (result.Width() < kWidthMargin) {
210 		// if resulting rect too narrow, make it a bit wider
211 		// for comfortable clicking
212 		if (column && column->Width() < kWidthMargin)
213 			result.right = result.left + column->Width();
214 		else
215 			result.right = result.left + kWidthMargin;
216 	}
217 	return result;
218 }
219 
220 
221 void
222 BTextWidget::MouseUp(BRect bounds, BPoseView *view, BPose *pose, BPoint,
223 	bool delayedEdit)
224 {
225 	// wait until a double click time to see if we are double clicking
226 	// or selecting widget for editing
227 	// start editing early if mouse left widget or modifier down
228 
229 	if (!IsEditable())
230 		return;
231 
232 	if (delayedEdit) {
233 		bigtime_t doubleClickTime;
234 		get_click_speed(&doubleClickTime);
235 		doubleClickTime += system_time();
236 
237 		while (system_time() < doubleClickTime) {
238 			// loop for double-click time and watch the mouse and keyboard
239 
240 			BPoint point;
241 			uint32 buttons;
242 			view->GetMouse(&point, &buttons, false);
243 			if (buttons)
244 				// if mouse button goes down then a double click, exit
245 				// without editing
246 				return;
247 
248 			if (!bounds.Contains(point))
249 				// mouse has moved outside of text widget so go into edit mode
250 				break;
251 
252 			if (modifiers() & (B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY | B_MENU_KEY))
253 				// watch the keyboard (ignoring standard locking keys)
254 				break;
255 
256 			snooze(100000);
257 		}
258 	}
259 
260 	StartEdit(bounds, view, pose);
261 }
262 
263 
264 static filter_result
265 TextViewFilter(BMessage *message, BHandler **, BMessageFilter *filter)
266 {
267 	uchar key;
268 	if (message->FindInt8("byte", (int8 *)&key) != B_OK)
269 		return B_DISPATCH_MESSAGE;
270 
271 	BPoseView *poseView = dynamic_cast<BContainerWindow*>(filter->Looper())->
272 		PoseView();
273 
274 	if (key == B_RETURN || key == B_ESCAPE) {
275 		poseView->CommitActivePose(key == B_RETURN);
276 		return B_SKIP_MESSAGE;
277 	}
278 
279 	if (key == B_TAB) {
280 		if (poseView->ActivePose()) {
281 			if (message->FindInt32("modifiers") & B_SHIFT_KEY)
282 				poseView->ActivePose()->EditPreviousWidget(poseView);
283 			else
284 				poseView->ActivePose()->EditNextWidget(poseView);
285 		}
286 
287 		return B_SKIP_MESSAGE;
288 	}
289 
290 	// the BTextView doesn't respect window borders when resizing itself;
291 	// we try to work-around this "bug" here.
292 
293 	// find the text editing view
294 	BView *scrollView = poseView->FindView("BorderView");
295 	if (scrollView != NULL) {
296 		BTextView *textView = dynamic_cast<BTextView *>(scrollView->FindView("WidgetTextView"));
297 		if (textView != NULL) {
298 			BRect rect = scrollView->Frame();
299 
300 			if (rect.right + 3 > poseView->Bounds().right
301 				|| rect.left - 3 < 0)
302 				textView->MakeResizable(true, NULL);
303 		}
304 	}
305 
306 	return B_DISPATCH_MESSAGE;
307 }
308 
309 
310 void
311 BTextWidget::StartEdit(BRect bounds, BPoseView *view, BPose *pose)
312 {
313 	if (!IsEditable())
314 		return;
315 
316 	// don't allow editing of the trash directory name
317 	BEntry entry(pose->TargetModel()->EntryRef());
318 	if (entry.InitCheck() == B_OK && FSIsTrashDir(&entry))
319 		return;
320 
321 	// don't allow editing of the "Disks" icon name
322 	if (pose->TargetModel()->IsRoot())
323 		return;
324 
325 	if (!ConfirmChangeIfWellKnownDirectory(&entry, "rename"))
326 		return;
327 
328 	// get bounds with full text length
329 	BRect rect(bounds);
330 	BRect textRect(bounds);
331 	rect.OffsetBy(-2, -1);
332 	rect.right += 1;
333 
334 	BFont font;
335 	view->GetFont(&font);
336 	BTextView *textView = new BTextView(rect, "WidgetTextView", textRect, &font, 0,
337 		B_FOLLOW_ALL, B_WILL_DRAW);
338 
339 	textView->SetWordWrap(false);
340 	DisallowMetaKeys(textView);
341 	fText->SetUpEditing(textView);
342 
343 	textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewFilter));
344 
345 	rect.right = rect.left + textView->LineWidth() + 3;
346 	// center new width, if necessary
347 	if (view->ViewMode() == kIconMode
348 		|| view->ViewMode() == kListMode && fAlignment == B_ALIGN_CENTER)
349 		rect.OffsetBy(bounds.Width() / 2 - rect.Width() / 2, 0);
350 
351 	rect.bottom = rect.top + textView->LineHeight() + 1;
352 	textRect = rect.OffsetToCopy(2, 1);
353 	textRect.right -= 3;
354 	textRect.bottom--;
355 	textView->SetTextRect(textRect);
356 
357 	textRect = view->Bounds();
358 	bool hitBorder = false;
359 	if (rect.left < 1)
360 		rect.left = 1, hitBorder = true;
361 	if (rect.right > textRect.right)
362 		rect.right = textRect.right - 2, hitBorder = true;
363 
364 	textView->MoveTo(rect.LeftTop());
365 	textView->ResizeTo(rect.Width(), rect.Height());
366 
367 	BScrollView *scrollView = new BScrollView("BorderView", textView, 0, 0, false,
368 		false, B_PLAIN_BORDER);
369 	view->AddChild(scrollView);
370 
371 	// configure text view
372 	switch (view->ViewMode()) {
373 		case kIconMode:
374 			textView->SetAlignment(B_ALIGN_CENTER);
375 			break;
376 
377 		case kMiniIconMode:
378 			textView->SetAlignment(B_ALIGN_LEFT);
379 			break;
380 
381 		case kListMode:
382 			textView->SetAlignment(fAlignment);
383 			break;
384 	}
385 	textView->MakeResizable(true, hitBorder ? NULL : scrollView);
386 
387 	view->SetActivePose(pose);		// tell view about pose
388 	SetActive(true);				// for widget
389 
390 	textView->SelectAll();
391 	textView->MakeFocus();
392 
393 	// make this text widget invisible while we edit it
394 	SetVisible(false);
395 
396 	ASSERT(view->Window());	// how can I not have a Window here???
397 
398 	if (view->Window())
399 		// force immediate redraw so TextView appears instantly
400 		view->Window()->UpdateIfNeeded();
401 }
402 
403 
404 void
405 BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView *view,
406 	BPose *pose, int32 poseIndex)
407 {
408 	// find the text editing view
409 	BView *scrollView = view->FindView("BorderView");
410 	ASSERT(scrollView);
411 	if (!scrollView)
412 		return;
413 
414 	BTextView *textView = dynamic_cast<BTextView *>(scrollView->FindView("WidgetTextView"));
415 	ASSERT(textView);
416 	if (!textView)
417 		return;
418 
419 	BColumn *column = view->ColumnFor(fAttrHash);
420 	ASSERT(column);
421 	if (!column)
422 		return;
423 
424 	if (saveChanges && fText->CommitEditedText(textView)) {
425 		// we have an actual change, re-sort
426 		view->CheckPoseSortOrder(pose, poseIndex);
427 	}
428 
429 	// make text widget visible again
430 	SetVisible(true);
431 	view->Invalidate(ColumnRect(poseLoc, column, view));
432 
433 	// force immediate redraw so TEView disappears
434 	scrollView->RemoveSelf();
435 	delete scrollView;
436 
437 	ASSERT(view->Window());
438 	view->Window()->UpdateIfNeeded();
439 	view->MakeFocus();
440 
441 	SetActive(false);
442 }
443 
444 
445 void
446 BTextWidget::CheckAndUpdate(BPoint loc, const BColumn *column, BPoseView *view)
447 {
448 	BRect oldRect;
449 	if (view->ViewMode() != kListMode)
450 		oldRect = CalcOldRect(loc, column, view);
451 
452 	if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view)) {
453 		BRect invalRect(ColumnRect(loc, column, view));
454 		if (view->ViewMode() != kListMode)
455 			invalRect = invalRect | oldRect;
456 		view->Invalidate(invalRect);
457 	}
458 }
459 
460 
461 void
462 BTextWidget::SelectAll(BPoseView *view)
463 {
464 	BTextView *text = dynamic_cast<BTextView *>(view->FindView("WidgetTextView"));
465 	if (text)
466 		text->SelectAll();
467 }
468 
469 
470 void
471 BTextWidget::Draw(BRect eraseRect, BRect textRect, float, BPoseView *view,
472 	BView *drawView, bool selected, uint32 clipboardMode, BPoint offset, bool direct)
473 {
474 	if (direct) {
475 		// erase area we're going to draw in
476 		if (view->EraseWidgetTextBackground() || selected) {
477 			drawView->SetDrawingMode(B_OP_COPY);
478 			eraseRect.OffsetBy(offset);
479 			drawView->FillRect(eraseRect, B_SOLID_LOW);
480 		} else
481 			drawView->SetDrawingMode(B_OP_OVER);
482 
483 		// set high color
484 		rgb_color highColor;
485 		if (view->IsDesktopWindow()) {
486 			if (selected)
487 				highColor = kWhite;
488 			else
489 				highColor = view->DeskTextColor();
490 		} else if (selected && view->Window()->IsActive() && !view->EraseWidgetTextBackground()) {
491 			highColor = kWhite;
492 		} else
493 			highColor = kBlack;
494 
495 		if (clipboardMode == kMoveSelectionTo && !selected) {
496 			view->SetDrawingMode(B_OP_ALPHA);
497 			view->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
498 			highColor.alpha = 64;
499 		}
500 		drawView->SetHighColor(highColor);
501 	}
502 
503 	BPoint loc;
504 	textRect.OffsetBy(offset);
505 
506 	loc.y = textRect.bottom - view->FontInfo().descent;
507 	loc.x = textRect.left + 1;
508 
509 	drawView->MovePenTo(loc);
510 	drawView->DrawString(fText->FittingText(view));
511 
512 	if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) {
513 		// ToDo:
514 		// this should be exported to the WidgetAttribute class, probably
515 		// by having a per widget kind style
516 		if (direct)
517 			drawView->SetHighColor(125, 125, 125);
518 
519 		textRect.right = textRect.left + fText->Width(view);
520 			// only underline text part
521 		drawView->StrokeLine(textRect.LeftBottom(), textRect.RightBottom(),
522 			B_MIXED_COLORS);
523 	}
524 }
525