1/*
2
3MarginView.cpp
4
5Copyright (c) 2002 Haiku.
6
7Authors:
8	Philippe Houdoin
9	Simon Gauvin
10	Michael Pfeiffer
11
12Permission is hereby granted, free of charge, to any person obtaining a copy of
13this software and associated documentation files (the "Software"), to deal in
14the Software without restriction, including without limitation the rights to
15use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
16of the Software, and to permit persons to whom the Software is furnished to do
17so, subject to the following conditions:
18
19The above copyright notice and this permission notice shall be included in all
20copies or substantial portions of the Software.
21
22THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28THE SOFTWARE.
29
30	Todo:
31
32	2 Make Strings constants or UI resources
33
34*/
35
36#include "MarginView.h"
37
38
39#include <AppKit.h>
40#include <GridView.h>
41#include <GridLayout.h>
42#include <GroupLayout.h>
43#include <GroupLayoutBuilder.h>
44#include <SupportKit.h>
45#include <TextControl.h>
46
47
48#include <stdio.h>
49#include <stdlib.h>
50
51
52/*----------------- MarginView Private Constants --------------------*/
53
54const int kOffsetY = 20;
55const int kOffsetX = 10;
56const int kStringSize = 50;
57const int kWidth = 50;
58const int kNumCount = 10;
59
60const static float kPointUnits = 1; // 1 point = 1 point
61const static float kInchUnits = 72; // 1" = 72 points
62const static float kCMUnits = 28.346; // 72/2.54 1cm = 28.346 points
63
64const static float kMinFieldWidth = 100; // pixels
65const static float kMinUnitHeight = 30; // pixels
66
67const static float kUnitFormat[] = { kInchUnits, kCMUnits, kPointUnits };
68const static char *kUnitNames[] = { "inch", "cm", "points", NULL };
69const static MarginUnit kUnitMsg[] = { kUnitInch, kUnitCM, kUnitPoint };
70
71const pattern kDots = {{ 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 }};
72
73const rgb_color kBlack 	= { 0,0,0,0 };
74const rgb_color kRed 	= { 255,0,0,0 };
75const rgb_color kWhite 	= { 255,255,255,0 };
76const rgb_color kGray 	= { 220,220,220,0 };
77
78
79PageView::PageView()
80: BView("pageView", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
81, fPageWidth(0)
82, fPageHeight(0)
83, fMargins(0, 0, 0, 0)
84{
85
86}
87
88
89void
90PageView::SetPageSize(float pageWidth, float pageHeight)
91{
92	fPageWidth = pageWidth;
93	fPageHeight = pageHeight;
94}
95
96
97void
98PageView::SetMargins(BRect margins)
99{
100	fMargins = margins;
101}
102
103
104void
105PageView::Draw(BRect bounds)
106{
107	BRect frame(Frame());
108	float totalWidth = frame.Width();
109	float totalHeight = frame.Height();
110
111	// fit page into available space
112	// keeping the ratio fPageWidth : fPageHeight
113	float pageWidth = totalWidth;
114	float pageHeight = totalWidth * fPageHeight / fPageWidth;
115	if (pageHeight > totalHeight) {
116		pageHeight = totalHeight;
117		pageWidth = totalHeight * fPageWidth / fPageHeight;
118	}
119
120	// center page
121	BPoint offset(0, 0);
122	offset.x = static_cast<int>((totalWidth - pageWidth) / 2);
123	offset.y = static_cast<int>((totalHeight - pageHeight) / 2);
124
125	// draw the page
126	SetHighColor(kWhite);
127	BRect r = BRect(0, 0, pageWidth, pageHeight);
128	r.OffsetBy(offset);
129	FillRect(r);
130	SetHighColor(kBlack);
131	StrokeRect(r);
132
133	// draw margin
134	SetHighColor(kRed);
135	SetLowColor(kWhite);
136	r.top += (fMargins.top / fPageHeight) * pageHeight;
137	r.right -= (fMargins.right / fPageWidth) * pageWidth;
138	r.bottom -= (fMargins.bottom / fPageHeight) * pageHeight;
139	r.left += (fMargins.left / fPageWidth) * pageWidth;
140	StrokeRect(r, kDots);
141}
142
143
144/**
145 * Constructor
146 *
147 * @param pageWidth, float that is the points value of the page width
148 * @param pageHeight, float that is the points value of the page height
149 * @param margins, BRect values of margins
150 * @param units, unit32 enum for units used in view
151 * @return void
152 */
153MarginView::MarginView(int32 pageWidth, int32 pageHeight,
154	BRect margins, MarginUnit units)
155	: BBox("marginView")
156{
157	fMarginUnit = units;
158	fUnitValue = kUnitFormat[units];
159
160	SetLabel("Margins");
161
162	fMargins = margins;
163
164	fPageWidth = pageWidth;
165	fPageHeight = pageHeight;
166}
167
168
169/**
170 * Destructor
171 *
172 * @param none
173 * @return void
174 */
175MarginView::~MarginView()
176{
177}
178
179
180/**
181 * AttachToWindow
182 *
183 * @param none
184 * @return void
185 */
186void
187MarginView::AttachedToWindow()
188{
189	if (Parent())
190		SetViewColor(Parent()->ViewColor());
191
192	_ConstructGUI();
193}
194
195
196/**
197 * MesssageReceived()
198 *
199 * Receive messages for the view
200 *
201 * @param BMessage* , the message being received
202 * @return void
203 */
204void
205MarginView::MessageReceived(BMessage *msg)
206{
207	switch (msg->what) {
208		case CHANGE_PAGE_SIZE: {
209				float w;
210				float h;
211				msg->FindFloat("width", &w);
212				msg->FindFloat("height", &h);
213				SetPageSize(w, h);
214				UpdateView(MARGIN_CHANGED);
215			}	break;
216
217		case FLIP_PAGE: {
218				BPoint p = PageSize();
219				SetPageSize(p.y, p.x);
220				UpdateView(MARGIN_CHANGED);
221			}	break;
222
223		case MARGIN_CHANGED:
224			UpdateView(MARGIN_CHANGED);
225			break;
226
227		case TOP_MARGIN_CHANGED:
228			UpdateView(TOP_MARGIN_CHANGED);
229			break;
230
231		case LEFT_MARGIN_CHANGED:
232			UpdateView(LEFT_MARGIN_CHANGED);
233			break;
234
235		case RIGHT_MARGIN_CHANGED:
236			UpdateView(RIGHT_MARGIN_CHANGED);
237			break;
238
239		case BOTTOM_MARGIN_CHANGED:
240			UpdateView(BOTTOM_MARGIN_CHANGED);
241			break;
242
243		case MARGIN_UNIT_CHANGED: {
244		 		int32 marginUnit;
245		 		if (msg->FindInt32("marginUnit", &marginUnit) == B_OK)
246					_SetMarginUnit((MarginUnit)marginUnit);
247			}	break;
248
249		default:
250			BView::MessageReceived(msg);
251			break;
252	}
253}
254
255
256/*----------------- MarginView Public Methods --------------------*/
257
258/**
259 * PageSize
260 *
261 * @param none
262 * @return BPoint, contains actual point values of page in x, y of point
263 */
264BPoint
265MarginView::PageSize() const
266{
267	return BPoint(fPageWidth, fPageHeight);
268}
269
270
271/**
272 * SetPageSize
273 *
274 * @param pageWidth, float that is the unit value of the page width
275 * @param pageHeight, float that is the unit value of the page height
276 * @return void
277 */
278void
279MarginView::SetPageSize(float pageWidth, float pageHeight)
280{
281	fPageWidth = pageWidth;
282	fPageHeight = pageHeight;
283}
284
285
286/**
287 * Margin
288 *
289 * @param none
290 * @return rect, return margin values always in points
291 */
292BRect
293MarginView::Margin() const
294{
295	BRect margin;
296
297	// convert the field text to values
298	float top = atof(fTop->Text());
299	float right = atof(fRight->Text());
300	float left = atof(fLeft->Text());
301	float bottom = atof(fBottom->Text());
302
303	// convert to units to points
304	switch (fMarginUnit) {
305		case kUnitInch:
306			// convert to points
307			top *= kInchUnits;
308			right *= kInchUnits;
309			left *= kInchUnits;
310			bottom *= kInchUnits;
311			break;
312		case kUnitCM:
313			// convert to points
314			top *= kCMUnits;
315			right *= kCMUnits;
316			left *= kCMUnits;
317			bottom *= kCMUnits;
318			break;
319		case kUnitPoint:
320			break;
321	}
322
323	margin.Set(left, top, right, bottom);
324
325	return margin;
326}
327
328
329
330/**
331 * Unit
332 *
333 * @param none
334 * @return uint32 enum, units in inches, cm, points
335 */
336MarginUnit
337MarginView::Unit() const
338{
339	return fMarginUnit;
340}
341
342
343/**
344 * UpdateView, recalculate and redraw the view
345 *
346 * @param msg is a message to the calculate size to tell which field caused
347 *		the update to occur, or it is a general update.
348 * @return void
349 */
350void
351MarginView::UpdateView(uint32 msg)
352{
353	Window()->Lock();
354
355	{
356		char pageSize[kStringSize];
357		sprintf(pageSize, "%2.1f x %2.1f",
358			fPageWidth / fUnitValue,
359			fPageHeight / fUnitValue);
360		fPageSize->SetText(pageSize);
361
362		fPage->SetPageSize(fPageWidth, fPageHeight);
363		fPage->SetMargins(Margin());
364		fPage->Invalidate();
365	}
366
367	Invalidate();
368	Window()->Unlock();
369}
370
371
372/*----------------- MarginView Private Methods --------------------*/
373
374/**
375 * _ConstructGUI()
376 *
377 * Creates the GUI for the View. MUST be called AFTER the View is attached to
378 *	the Window, or will crash and/or create strange behaviour
379 *
380 * @param none
381 * @return void
382 */
383void
384MarginView::_ConstructGUI()
385{
386	fPage = new PageView();
387	fPage->SetViewColor(ViewColor());
388
389	fPageSize = new BStringView("pageSize", "?x?");
390
391	BString str;
392	// Create text fields
393
394	// top
395	str << fMargins.top/fUnitValue;
396	fTop = new BTextControl("top", "Top:", str.String(), NULL);
397
398	fTop->SetModificationMessage(new BMessage(TOP_MARGIN_CHANGED));
399	fTop->SetTarget(this);
400	_AllowOnlyNumbers(fTop, kNumCount);
401
402	//left
403    str = "";
404	str << fMargins.left/fUnitValue;
405	fLeft = new BTextControl("left", "Left:", str.String(), NULL);
406
407	fLeft->SetModificationMessage(new BMessage(LEFT_MARGIN_CHANGED));
408	fLeft->SetTarget(this);
409	_AllowOnlyNumbers(fLeft, kNumCount);
410
411	//bottom
412    str = "";
413	str << fMargins.bottom/fUnitValue;
414	fBottom = new BTextControl("bottom", "Bottom:", str.String(), NULL);
415
416	fBottom->SetModificationMessage(new BMessage(BOTTOM_MARGIN_CHANGED));
417	fBottom->SetTarget(this);
418	_AllowOnlyNumbers(fBottom, kNumCount);
419
420	//right
421    str = "";
422	str << fMargins.right/fUnitValue;
423	fRight = new BTextControl("right", "Right:", str.String(), NULL);
424
425	fRight->SetModificationMessage(new BMessage(RIGHT_MARGIN_CHANGED));
426	fRight->SetTarget(this);
427	_AllowOnlyNumbers(fRight, kNumCount);
428
429	// Create Units popup
430
431	BPopUpMenu *menu = new BPopUpMenu("units");
432	BMenuField *units = new BMenuField("units", "Units:", menu);
433
434	BMenuItem *item;
435	// Construct menu items
436	for (int32 i = 0; kUnitNames[i] != NULL; i++) {
437		BMessage *msg = new BMessage(MARGIN_UNIT_CHANGED);
438		msg->AddInt32("marginUnit", kUnitMsg[i]);
439		menu->AddItem(item = new BMenuItem(kUnitNames[i], msg));
440		item->SetTarget(this);
441		if (fMarginUnit == kUnitMsg[i])
442			item->SetMarked(true);
443	}
444
445	BGridView* settings = new BGridView();
446	BGridLayout* settingsLayout = settings->GridLayout();
447	settingsLayout->AddItem(fTop->CreateLabelLayoutItem(), 0, 0);
448	settingsLayout->AddItem(fTop->CreateTextViewLayoutItem(), 1, 0);
449	settingsLayout->AddItem(fLeft->CreateLabelLayoutItem(), 0, 1);
450	settingsLayout->AddItem(fLeft->CreateTextViewLayoutItem(), 1, 1);
451	settingsLayout->AddItem(fBottom->CreateLabelLayoutItem(), 0, 2);
452	settingsLayout->AddItem(fBottom->CreateTextViewLayoutItem(), 1, 2);
453	settingsLayout->AddItem(fRight->CreateLabelLayoutItem(), 0, 3);
454	settingsLayout->AddItem(fRight->CreateTextViewLayoutItem(), 1, 3);
455	settingsLayout->AddItem(units->CreateLabelLayoutItem(), 0, 4);
456	settingsLayout->AddItem(units->CreateMenuBarLayoutItem(), 1, 4);
457	settingsLayout->SetSpacing(0, 0);
458
459	BGroupView* groupView = new BGroupView(B_HORIZONTAL, 10);
460	BGroupLayout* groupLayout = groupView->GroupLayout();
461	groupLayout->AddView(BGroupLayoutBuilder(B_VERTICAL, 0)
462		.Add(fPage)
463		.Add(fPageSize)
464		.SetInsets(0, 0, 0, 0)
465		.TopView()
466	);
467	groupLayout->AddView(settings);
468	groupLayout->SetInsets(5, 5, 5, 5);
469
470	AddChild(groupView);
471
472	UpdateView(MARGIN_CHANGED);
473}
474
475
476/**
477 * AllowOnlyNumbers()
478 *
479 * @param BTextControl, the control we want to only allow numbers
480 * @param maxNum, the maximun number of characters allowed
481 * @return void
482 */
483void
484MarginView::_AllowOnlyNumbers(BTextControl *textControl, int32 maxNum)
485{
486	BTextView *tv = textControl->TextView();
487
488	for (int32 i = 0; i < 256; i++)
489		tv->DisallowChar(i);
490
491	for (int32 i = '0'; i <= '9'; i++)
492		tv->AllowChar(i);
493
494	tv->AllowChar(B_BACKSPACE);
495	// TODO internationalization; e.g. "." or ","
496	tv->AllowChar('.');
497	tv->SetMaxBytes(maxNum);
498}
499
500/**
501 * SetMargin
502 *
503 * @param brect, margin values in rect
504 * @return void
505 */
506void
507MarginView::_SetMargin(BRect margin)
508{
509	fMargins = margin;
510}
511
512
513/**
514 * SetUnits, called by the MarginMgr when the units popup is selected
515 *
516 * @param uint32, the enum that identifies the units requested to change to.
517 * @return void
518 */
519void
520MarginView::_SetMarginUnit(MarginUnit unit)
521{
522	// do nothing if the current units are the same as requested
523	if (unit == fMarginUnit) {
524		return;
525	}
526
527	// set the units Format
528	fUnitValue = kUnitFormat[unit];
529
530	// convert the field text to values
531	float top = atof(fTop->Text());
532	float right = atof(fRight->Text());
533	float left = atof(fLeft->Text());
534	float bottom = atof(fBottom->Text());
535
536	// convert to target units
537	switch (fMarginUnit)
538	{
539		case kUnitInch:
540			// convert to points
541			top *= kInchUnits;
542			right *= kInchUnits;
543			left *= kInchUnits;
544			bottom *= kInchUnits;
545			// check for target unit is cm
546			if (unit == kUnitCM) {
547				top /= kCMUnits;
548				right /= kCMUnits;
549				left /= kCMUnits;
550				bottom /= kCMUnits;
551			}
552			break;
553		case kUnitCM:
554			// convert to points
555			top *= kCMUnits;
556			right *= kCMUnits;
557			left *= kCMUnits;
558			bottom *= kCMUnits;
559			// check for target unit is inches
560			if (unit == kUnitInch) {
561				top /= kInchUnits;
562				right /= kInchUnits;
563				left /= kInchUnits;
564				bottom /= kInchUnits;
565			}
566			break;
567		case kUnitPoint:
568			// check for target unit is cm
569			if (unit == kUnitCM) {
570				top /= kCMUnits;
571				right /= kCMUnits;
572				left /= kCMUnits;
573				bottom /= kCMUnits;
574			}
575			// check for target unit is inches
576			if (unit == kUnitInch) {
577				top /= kInchUnits;
578				right /= kInchUnits;
579				left /= kInchUnits;
580				bottom /= kInchUnits;
581			}
582			break;
583	}
584	fMarginUnit = unit;
585
586	// lock Window since these changes are from another thread
587	Window()->Lock();
588
589	// set the fields to new units
590	BString str;
591	str << top;
592	fTop->SetText(str.String());
593
594	str = "";
595	str << left;
596	fLeft->SetText(str.String());
597
598	str = "";
599	str << right;
600	fRight->SetText(str.String());
601
602	str = "";
603	str << bottom;
604	fBottom->SetText(str.String());
605
606	// update UI
607	Invalidate();
608
609	Window()->Unlock();
610}
611
612